import _ from 'lodash';

/** 根据姓名存储 websocket 实例 */
type WebSocketInstance = WebSocket & {
  /** 心跳信息 */
  heartInfo?: HeartOptions;
  /** 初始化 websocket 后的回调函数 */
  callback?: (data?: any) => void;
};

/** websocket 选项 */
interface WebSocketOptions {
  /** 连接名称 */
  socketId: string;
  /** 心跳包数据 */
  heart?: Heart;
  /** 发送心跳包间隔时间 */
  timeout?: number;
  /** 初始化后的回调函数 */
  callback?: (data?: any) => void;
}

/** websocket 心跳 */
interface Heart {
  /** 心跳 code */
  code: string;
  /** 心跳信息 */
  message: string;
  /** 连接名称 */
  socketId?: string;
}

/** websocket 心跳选项 */
interface HeartOptions {
  /** 心跳包 */
  heart: Heart;
  /** 每段时间发送一次心跳包 这里设置为60s */
  timeout: number;
  /** 延时发送消息对象(启动心跳新建这个对象，收到消息后重置对象) */
  heartbeat?: NodeJS.Timeout;
  /** 开始心跳 */
  start(key?: string): void;
  /** 重置心跳 */
  reset(): void;
  /** 清除心跳 */
  clear(): void;
}

/** 根据姓名存储 websocket 实例 */
const webSocket: Record<string, WebSocketInstance> = {};
/** 连接状态 */
const connectState: Record<string, boolean> = {};
/** websocket 连接地址 */
const websocketUrl = import.meta.env.DEV ? `ws://192.168.254.9:8080/userWS/` : `wss://${window.location.host}/userWS/`;
/** 心跳设置 */
const heartOptions: HeartOptions = {
  /** 心跳包 */
  heart: { code: '11111', message: 'session heart' },
  /** 每段时间发送一次心跳包 这里设置为60s */
  timeout: 60 * 1000,
  /** 延时发送消息对象(启动心跳新建这个对象，收到消息后重置对象) */
  heartbeat: undefined,
  /** 开始心跳 */
  start(key?: string) {
    this.heartbeat = setInterval(() => {
      if (key && connectState[key]) {
        webSocketSend(key, this.heart);
      } else {
        this.clear();
      }
    }, this.timeout);
  },
  /** 重置心跳 */
  reset() {
    clearInterval(this.heartbeat);
    this.start();
  },
  /** 清除心跳 */
  clear() {
    clearInterval(this.heartbeat);
  }
};

/**
 * 初始化 websocket
 *
 * @param socketId 连接名称
 * @param heart 心跳包数据
 * @param timeout 发送心跳包间隔时间
 * @param callback 初始化后的回调函数
 * @returns void
 */
export function initWebSocket({ socketId, heart, timeout, callback }: WebSocketOptions) {
  /** 该浏览器不支持websocket */
  if (!('WebSocket' in window)) return;

  webSocket[socketId] = new WebSocket(websocketUrl + socketId); // 创建socket对象

  /** 此callback为在其他地方调用时定义的接收socket数据的函数 */
  if (callback) {
    webSocket[socketId].callback = callback;
  }

  /** 连接成功建立的回调方法 */
  webSocket[socketId].onopen = () => {
    const heartInfo = _.cloneDeep(heartOptions);
    /** 首次握手 */
    if (timeout) heartInfo.timeout = timeout;
    if (heart) heartInfo.heart = heart;
    /** 发送心跳包 */
    webSocketSend(socketId, heartInfo.heart);
    connectState[socketId] = true;
    webSocket[socketId].heartInfo = heartInfo;
    webSocket[socketId].heartInfo.start(socketId);
  };

  /** 接收到消息的回调方法 */
  webSocket[socketId].onmessage = e => {
    /** 根据自己的需要对接收到的数据进行格式化 */
    const data = JSON.parse(e.data);
    const cb = webSocket[socketId].callback;
    /** 将 data 传给在外定义的接收数据的函数 */
    if (cb) cb(data);
  };

  /** 连接关闭的回调方法 */
  webSocket[socketId].onclose = () => {
    if (!webSocket[socketId].heartInfo) return;
    webSocket[socketId].heartInfo.clear();
    /** 断开后修改标识 */
    connectState[socketId] = false;
    const cb = webSocket[socketId].callback;
    /** 将 data 传给在外定义的接收数据的函数 */
    if (cb) cb({ code: '00000' });
  };

  /** 连接发生错误的回调方法 */
  webSocket[socketId].onerror = () => {
    if (!webSocket[socketId].heartInfo) return;
    webSocket[socketId].heartInfo.clear();
    /** 断开后修改标识 */
    connectState[socketId] = false;
    const cb = webSocket[socketId].callback;
    /** 将 data 传给在外定义的接收数据的函数 */
    if (cb) cb({ code: '10002' });
  };
}

/**
 * 发送数据
 *
 * @param socketId 连接名称
 * @param data 发送的原始数据
 * @returns void
 */
function webSocketSend(socketId: string, data: any) {
  /** 根据自己的需要转换数据格式 */
  webSocket[socketId].send(JSON.stringify(data));
}

/**
 * 手动关闭 websocket
 *
 * @param socketId 连接名（不传则关闭所有 socket）
 * @returns void
 */
export function closeWebSocket(socketId?: string) {
  if (socketId) {
    if (!webSocket[socketId]?.heartInfo) return;
    webSocket[socketId].close();
    webSocket[socketId].heartInfo.clear();
    connectState[socketId] = false;
  } else {
    for (const k in webSocket) {
      if (Object.hasOwn(webSocket, k)) {
        webSocket[k]?.close();
        webSocket[k]?.heartInfo?.clear();
      }
    }
    for (const k in connectState) {
      if (Object.hasOwn(connectState, k)) {
        connectState[k] = false;
      }
    }
  }
}
